/*file chiron src/nihilogic.dk/pixastic.js */

/*preamble-jacob-seidelin
    Pixastic Lib - Core Functions - v0.1.0
    Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
    MIT License [http://www.opensource.org/licenses/mit-license.php]
*/

/*preamble

    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    The license terms are stated in full in <license.rst> and at the end
    of all source files.

*/

/**
    A JavaScript and Canvas image manipulation library by Jacob Seidelin.
    <http://pixastic.com/lib/docs/>
*/

/*port-notes

     - remove the enclosure.
     - convert the return to exports.
     - remove the singleton code from init since modules.js does that.
     - remove the addEvent and onready code.
       All of that is done already by modules.js.
     - Don't just call init.  Rename it "processDom" and export
       it so the user can opt to call it.  Factor out a "processElement"
       function so that users can use their own CSS selector engine.
     - change all "if debug writeDebug" patterns to "error", and remove
       the writeDebug function, since it's provided by modules.js.
       Remove the "debugElement".  Remove the "debug" export.
     - remove the "parseOnLoad" export since it appears to be superfluous.
     - include environment.js and simplify the "isIE" expression, since
       modules.js provides this information already.  Remove the Client.isIE
       function.
     - promote hasCanvas and hasCanvasData to exported functions.  Remove
       the Pixastic.Client qualifications on references to those functions.
       Make sure to simplify them in all of the action files too.
     - add the "aetAction" function to use modules.js's "require" function
       to grab singleton action modules, instead of prepopulating
       them in the Action object.  Remove the Action object.  Change
       all references to the Action object to calls to the action 
       function.
     - change all action modules into duck modules that provide
       "process" and "checkSupport" methods, instead of augmenting
       Pixastic.Actions.
     - remove remaining "Pixastic." qualifiactions since they'll be
       implicitly in scope when added to the exports object.
     - reindent getElementsByClassName from 8 to 4 spaces.  Consider
       removing it some day, along with ``processDom``, or generally
       in favor of using one of the several selector engines
       in Chiron.
    
*/

include('environment');

/*** processElement
*/
exports.processElement = function (el) {
    var actions = [];
    var classes = el.className.split(" ");
    for (var c=0;c<classes.length;c++) {
        var cls = classes[c];
        if (cls.substring(0,9) == "pixastic-") {
            var actionName = cls.substring(9);
            if (actionName != "")
                actions.push(actionName);
        }
    }
    if (actions.length) {
        if (el.tagName == "IMG") {
            var dataImg = new Image();
            dataImg.src = el.src;
            if (dataImg.complete) {
                for (var a=0;a<actions.length;a++) {
                    var res = applyAction(el, el, actions[a], null);
                    if (res) 
                        el = res;
                }
            } else {
                dataImg.onload = function() {
                    for (var a=0;a<actions.length;a++) {
                        var res = applyAction(el, el, actions[a], null)
                        if (res) 
                            el = res;
                    }
                }
            }
        } else {
            setTimeout(function() {
                for (var a=0;a<actions.length;a++) {
                    var res = applyAction(
                        el, el, actions[a], null
                    );
                    if (res) 
                        el = res;
                }
            },1);
        }
    }
};

/*** processDom
*/
exports.processDom = function () {
    var imgEls = getElementsByClass("pixastic", null, "img");
    var canvasEls = getElementsByClass("pixastic", null, "canvas");
    var elements = imgEls.concat(canvasEls);
    for (var i=0;i<elements.length;i++) {
        processElement(elements[i]);
    }
}

/*** process
    Applies a transofmration to an image or canvas.
    Accepts:

     - an image or canvas DOM element,
     - an action name, the basename for one of the modules
       in ``./pixastic/actions``,
     - an options object for the transform, and
     - an optional callback for when the transform has
       completed.

*/
exports.process = function(img, actionName, options, callback) {
    if (img.tagName == "IMG") {
        var dataImg = new Image();
        dataImg.src = img.src;
        if (dataImg.complete) {
            var res = applyAction(img, dataImg, actionName, options);
            if (callback) callback(res);
            return res;
        } else {
            dataImg.onload = function() {
                var res = applyAction(img, dataImg, actionName, options)
                if (callback) callback(res);
            }
        }
    }
    if (img.tagName == "CANVAS") {
        var res = applyAction(img, img, actionName, options);
        if (callback) callback(res);
        return res;
    }
};

/*** getAction
    gets an action duck module object by importing the 
    corresponding module.
*/
exports.getAction = function (name) {
    return require('./pixastic/actions/' + name + '.js');
};

/*** applyAction
    does the heavy lifting for `process`.
*/
exports.applyAction = function(img, dataImg, actionName, options) {

    options = options || {};

    var imageIsCanvas = (img.tagName == "CANVAS");
    if (imageIsCanvas && isIE) {
        environment.print("Tried to process a canvas element but browser is IE.", 'error');
        return false;
    }

    var canvas, ctx;
    if (hasCanvas()) {
        canvas = document.createElement("canvas");
        ctx = canvas.getContext("2d");
    }

    var w = parseInt(img.offsetWidth);
    var h = parseInt(img.offsetHeight);

    if (actionName.indexOf("(") > -1) {
        var tmp = actionName;
        actionName = tmp.substr(0, tmp.indexOf("("));
        var arg = tmp.match(/\((.*?)\)/);
        if (arg[1]) {
            arg = arg[1].split(";");
            for (var a=0;a<arg.length;a++) {
                thisArg = arg[a].split("=");
                if (thisArg.length == 2) {
                    if (thisArg[0] == "rect") {
                        var rectVal = thisArg[1].split(",");
                        options[thisArg[0]] = {
                            left : parseInt(rectVal[0],10)||0,
                            top : parseInt(rectVal[1],10)||0,
                            width : parseInt(rectVal[2],10)||0,
                            height : parseInt(rectVal[3],10)||0
                        }
                    } else {
                        options[thisArg[0]] = thisArg[1];
                    }
                }
            }
        }
    }

    if (!options.rect) {
        options.rect = {
            left : 0, top : 0, width : w, height : h
        };
    }

    try {
        var action = getAction(actionName);
    } catch (exception) {
        throw new Error(
            "Not a valid action \"" + actionName + "\".  " +
            "The action module does not exist."
        );
    }
    if (typeof action.process != "function") {
        throw new Error(
            "Not a valid action \"" + actionName + "\".  " + 
            "The module must include a process function."
        );
    }

    if (!action.checkSupport()) {
        environment.print("Action \"" + actionName + "\" not supported by this browser.", 'error');
        return false;
    }

    if (hasCanvas()) {
        canvas.width = w;
        canvas.height = h;
        canvas.style.width = w+"px";
        canvas.style.height = h+"px";
        ctx.drawImage(dataImg,0,0,w,h);
    }

    var params = {
        image : img,
        canvas : canvas,
        width : w,
        height : h,
        useData : true,
        options : options
    }

    //var time = new Date().getTime();

    var res = action.process(params);

    //console.log("Time:",new Date().getTime() - time,"ms");

    if (!res) {
        return false;
    }

    if (hasCanvas()) {
        if (params.useData) {
            if (hasCanvasImageData()) {
                canvas.getContext("2d").putImageData(params.canvasData, options.rect.left, options.rect.top);
                // Opera doesn't seem to update the canvas until we draw something on it, lets draw a 0x0 rectangle.
                canvas.getContext("2d").fillRect(0,0,0,0);
            }
        }
        // copy properties and stuff from the source image
        canvas.title = img.title;
        canvas.imgsrc = img.imgsrc;
        if (!imageIsCanvas) canvas.alt  = img.alt;
        if (!imageIsCanvas) canvas.imgsrc = img.src;
        canvas.className = img.className;
        canvas.setAttribute("style", img.getAttribute("style"));
        canvas.cssText = img.cssText;
        canvas.name = img.name;
        canvas.tabIndex = img.tabIndex;
        canvas.id = img.id;

        if (img.parentNode && img.parentNode.replaceChild) {
            img.parentNode.replaceChild(canvas, img);
        }

        return canvas;
    }

    return img;
};

/*** prepareData
    a utility function used by some actions to get image data
    from a rectangle of a canvas.
*/
exports.prepareData = function(params, getCopy) {
    var ctx = params.canvas.getContext("2d");
    var rect = params.options.rect;
    var dataDesc = ctx.getImageData(rect.left, rect.top, rect.width, rect.height);
    var data = dataDesc.data;
    if (!getCopy) params.canvasData = dataDesc;
    return data;
};

/*** hasCanvas
*/
exports.hasCanvas = (function() {
    var c = document.createElement("canvas");
    var val = false;
    try {
        val = !!((typeof c.getContext == "function") && c.getContext("2d"));
    } catch(e) {}
    return function() {
        return val;
    }
})();

/*** hasCanvasImageData
*/
exports.hasCanvasImageData = (function() {
    var c = document.createElement("canvas");
    var val = false;
    var ctx;
    try {
        if (typeof c.getContext == "function" && (ctx = c.getContext("2d"))) {
            val = (typeof ctx.getImageData == "function");
        }
    } catch(e) {}
    return function() {
        return val;
    }
})();

// getElementsByClass by Dustin Diaz, http://www.dustindiaz.com/getelementsbyclass/
function getElementsByClass(searchClass,node,tag) {
    var classElements = new Array();
    if ( node == null )
        node = document;
    if ( tag == null )
        tag = '*';

    var els = node.getElementsByTagName(tag);
    var elsLen = els.length;
    var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
    for (i = 0, j = 0; i < elsLen; i++) {
        if ( pattern.test(els[i].className) ) {
            classElements[j] = els[i];
            j++;
        }
    }
    return classElements;
}


/*license

    Legal
    =======
    
    Chiron is a component of the Tale web-game project.
    
    See <credit.txt> for a complete list of
    contributions and their licenses.  All contributions are provided
    under permissive, non-viral licenses including MIT, BSD, Creative Commons
    Attribution 2.5, Public Domain, or Unrestricted.
    
    
    License
    =======
    
    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    
    MIT License
    -----------
    
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

